﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using SharpMap.Layers;
using SharpMap.Styles;
using Sustainalytics.Entities.RiskRatings;

namespace Sustainalytics.RiskRatings.Reporting
{
    public static class IncidentsMap
    {

        private static readonly VectorLayer _wvl;




        static IncidentsMap()
        {
            GeoAPI.GeometryServiceProvider.Instance = new NetTopologySuite.NtsGeometryServices();

            var _filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\world_adm0.shp");
            var _WorldShapeFile = new SharpMap.Data.Providers.ShapeFile(_filePath, true);
            _wvl = new VectorLayer("World", _WorldShapeFile);

            _WorldShapeFile = new SharpMap.Data.Providers.ShapeFile(_filePath, true);
            _wvl = new VectorLayer("World", _WorldShapeFile)
            {
                Style = new VectorStyle
                {
                    Fill = new SolidBrush(Color.FromArgb(154, 154, 154))

                }
            };
        }
        
      


        private static List<NetTopologySuite.Geometries.Geometry> GetIncidentPolygons(List<Location> Locations, MapSize mapSize)
        {
            var polygons = new List<NetTopologySuite.Geometries.Geometry>();

            //Merge overlaying locations
            for (var i = Locations.Count - 1; i > 0; )
            {
                var isDecremented = false;
                for (var j = i - 1; j >= 0; j--)
                {
                    if (LocationsAreOverlapping(Locations[i], Locations[j]))
                    {
                        Locations[i] = GetMidpointBetween2Locations(Locations[i], Locations[j]);
                        Locations.RemoveAt(j);
                        i--;
                        isDecremented = true;
                    }
                }
                if (!isDecremented)
                {
                    i--;
                }
            }

            var locationSize = new SizeF();
            switch (mapSize)
            {
                case MapSize.Small:
                    locationSize.Height = 8;
                    locationSize.Width = 8;
                    break;
                case MapSize.Big:
                    locationSize.Height = 4;
                    locationSize.Width = 4;
                    break;
            }

            Locations.ForEach(location =>
            {
                var outerCircle = CreateCircle(new NetTopologySuite.Geometries.Point(location.Longitude, location.Latitude), locationSize);
                var polygon = new NetTopologySuite.Geometries.Polygon(outerCircle);
                polygons.Add(polygon);

            });

            return polygons;
        }

        private static Location GetMidpointBetween2Locations(Location location1, Location location2)
        {
            return new Location()
            {
                Longitude = (location1.Longitude + location2.Longitude) / 2,
                Latitude = (location1.Latitude + location2.Latitude) / 2,
            };
        }

        private static bool LocationsAreOverlapping(Location location1, Location location2)
        {
            return Distance(location1.Latitude, location1.Longitude, location2.Latitude, location2.Longitude) < 1000;
        }

        private static double Distance(double lat1, double lon1, double lat2, double lon2)
        {
            double theta = lon1 - lon2;
            double dist = Math.Sin(Deg2Rad(lat1)) * Math.Sin(Deg2Rad(lat2)) + Math.Cos(Deg2Rad(lat1)) * Math.Cos(Deg2Rad(lat2)) * Math.Cos(Deg2Rad(theta));
            dist = Math.Acos(dist);
            dist = Rad2Deg(dist);
            dist = dist * 60 * 1.1515;
            dist = dist * 1.609344;//in km
            return (dist);
        }

        private static double Rad2Deg(double rad)
        {
            return (rad / Math.PI * 180.0);
        }

        private static double Deg2Rad(double deg)
        {
            return (deg * Math.PI / 180.0);
        }

        private static GeoAPI.Geometries.ILinearRing CreateCircle(NetTopologySuite.Geometries.Point center, System.Drawing.SizeF size)
        {
            return CreateCircle(center, size, 12);
        }
 
        private static NetTopologySuite.Geometries.LinearRing CreateCircle(NetTopologySuite.Geometries.Point center,
                                                                    System.Drawing.SizeF size,
                                                                    int segmentsPerQuadrant)
        {
            const double piHalf = System.Math.PI * 0.5d;

            var step = piHalf / segmentsPerQuadrant;

            var pts = new GeoAPI.Geometries.Coordinate[4 * segmentsPerQuadrant + 1];
            var angle = 0d;
            for (var i = 0; i < 4 * segmentsPerQuadrant; i++)
            {
                pts[i] = new GeoAPI.Geometries.Coordinate(center.X + Math.Cos(angle) * size.Width,
                                    center.Y + Math.Sin(angle) * size.Height);
                angle += step;
            }
            pts[pts.Length - 1] = pts[0];
            return new NetTopologySuite.Geometries.LinearRing(pts);
        }

        public static byte[] GetMapImageOptimized(List<Location> Locations, int width, int height, MapSize mapSize)
        {
            byte[] buffer;

            var incidentPolygons = GetIncidentPolygons(Locations, mapSize);


            using (SolidBrush worldBrush = new SolidBrush(Color.FromArgb(154,154,154)),
                           geometryBrush = new SolidBrush(Color.FromArgb(255, 169, 169, 169)),
                              pointBrush = new SolidBrush(Color.FromArgb(104, 170, 163)),
                       incidentFillBrush = new SolidBrush(Color.FromArgb(211, 211, 211)),
                             hqFillBrush = new SolidBrush(Color.FromArgb(65, 177, 166)))
            using (var pen = new Pen(Color.FromArgb(104, 170, 163)) { Width = 7 })
            {
                
                using (var worldMap = new SharpMap.Forms.MapBox { Height = height, Width = width })
                using (var ivl = new VectorLayer("IncidentsLayer"))
              
                {
                    worldMap.Map.Layers.Add(_wvl);


                    if (incidentPolygons.Any())
                    {
                        ivl.DataSource = new SharpMap.Data.Providers.GeometryProvider(incidentPolygons.ToArray());

                        if (mapSize == MapSize.Big)
                        {
                            ivl.Style = new VectorStyle { Fill = incidentFillBrush };
                        }
                        else
                        {
                            ivl.Style = new VectorStyle
                            {
                                Fill = geometryBrush,
                                Line =
                                {
                                    Color = Color.FromArgb(104, 170, 163),
                                    Width = 6
                                },
                                PointColor = pointBrush,
                                EnableOutline = true,
                                Outline = pen
                            };
                        }
                    }

                    if (ivl.DataSource != null)
                        worldMap.Map.Layers.Add(ivl);

                    worldMap.Map.ZoomToExtents();
                    worldMap.ActiveTool = SharpMap.Forms.MapBox.Tools.Pan;


                    //worldMap.Refresh(); async call generate null reference exception in WorldShapeFile 


                    using (var imgMap = worldMap.Map.GetMap(width))
                    using (var memoryStream = new MemoryStream())
                    {
                        imgMap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Png);
                        buffer = memoryStream.ToArray();
                    }
                }
            }

            return buffer;
        }
    }

    public enum MapSize
    {
        Small = 0,
        Big = 1
    }
}
